<template>
    <el-container>
        <el-container>
            <el-header>
                <el-scrollbar style="height: 40px;">
                    <div class="text-content__title">
                        <el-checkbox v-for="(item, key) in content_config" :key="key" :label="item.label"
                            v-model="item.value" :disabled="key != 'shellMode' && content_config.shellMode.value"
                            size="small" border />
                        <span style="position: absolute; right: 0;">
                            <el-tooltip placement="bottom" content="一秒内 接收/发送数据 个数 (数据刷新率)">
                                <el-tag type="primary">
                                    FPS:
                                    {{ fps }}
                                </el-tag>
                            </el-tooltip>
                            <el-tag type="primary" style="margin-left: 5px;">
                                显示帧数:
                                {{ show_items.length }}
                            </el-tag>
                        </span>
                    </div>
                </el-scrollbar>
            </el-header>
            <el-main>
                <el-scrollbar ref="scrollbarRef" :key="config.listen">
                    <DynamicScroller ref="scroller" v-if="show_items[0]" :items="show_items" :min-item-size="16"
                        :item-size="item_height" class="scroller" key-field="id" page-mode @resize="scrollerGoToBottom">
                        <template v-slot="{ item, index, active }">
                            <DynamicScrollerItem v-if="item" :item="item" :active="active" :data-index="index">
                                <el-text v-if="content_config.showTime.value"
                                    :type="item.type == 'RX' ? 'success' : 'primary'">
                                    [{{ item.id }}] [
                                    {{ new Date(item.time).toLocaleString() }}
                                    {{ numberToStringHaveZero(item.time % 1000, 3) }}
                                    ]
                                    {{ item.type }} {{ item.type == 'RX' ? '←' : '→' }} |
                                </el-text>
                                <el-text v-if="content_config.showText.value">
                                    {{ item.value }}
                                </el-text>
                                <el-text v-if="content_config.showHex.value" class="hexdump__item" style="display: block;">
                                    {{ stringToHex(item.value) }}
                                </el-text>
                                <el-tag v-if="content_config.showHexdump.value" type="primary" style="display: block;"
                                    class="hexdump" disable-transitions>
                                    <div v-for="(str, index) in splitStringBySize(item.value, 16)" :key="index">
                                        <b class="hexdump__item" style="width: 100px;">
                                            {{ numberToStringHaveZero(index * 16, 8) }} :
                                        </b>
                                        <b class="hexdump__item" style="width: 500px;">
                                            {{ stringToHex(str) }}
                                        </b>
                                        <b class="hexdump__item">
                                            |{{ arrayToHexString(str, 16) }}|
                                        </b>
                                    </div>
                                </el-tag>
                            </DynamicScrollerItem>
                        </template>
                    </DynamicScroller>
                </el-scrollbar>
            </el-main>
            <el-footer>
                <el-row>
                    <el-col :span="22">
                        <el-input v-model="send_text.textarea" style="padding: 0px 5px" autosize type="textarea"
                            :placeholder="send_text.hex ? '输入HEX格式' : '输入文本'" spellcheck=“false” clearable />
                        <el-button v-if="send_text.textarea.length" type="info"
                            style="height: 25px;width: 25px;margin: auto 0;" circle @click="send_text.textarea = ''">
                            <el-icon-Close width="20px" />
                        </el-button>
                    </el-col>
                    <el-col :span="2"><el-button type="primary">发送</el-button></el-col>
                </el-row>
                <el-row class="text-content-info" :gutter="0">
                    <span style="position: absolute;left: 0px;">
                        <el-button type="primary" plain @click="drawer = true">
                            扩展命令
                        </el-button>
                        <el-button type="primary" plain @click="">
                            压力测试
                        </el-button>
                        <el-button type="primary" plain @click="">
                            刷新
                        </el-button>
                        <el-tag>{{ times.refresh }}</el-tag>
                        <el-tag>{{ times.stress_rx }}</el-tag>
                        <el-tag>{{ times.stress_tx }}</el-tag>
                    </span>
                    <el-checkbox label="循环发送(ms)" v-model="loop_send.enable" size="small" border
                        @change="create_loop_tx_task(loop_send.enable)" />
                    <el-input-number v-model="loop_send.time_ms" :min="0" :step="10" :value-on-clear="0"
                        @change="create_loop_tx_task(loop_send.enable)" size="small" />
                    <el-button type="primary" plain @click="send_text.hex = !send_text.hex">
                        发送格式:{{ send_text.hex ? 'HEX' : '文本' }}
                    </el-button>
                    <el-button type="primary" plain @click="send_text.send.sum = send_text.send.success = 0">TX(B): 0/{{
                        send_text.send.sum }}</el-button>
                    <el-button type="success" plain @click="send_text.recv.sum = send_text.recv.success = 0">RX(B): {{
                        send_text.recv.sum }}</el-button>
                    <el-button type="info" plain @click="clear_buffer">
                        缓存帧数:
                        {{ buffer.length }}/
                        {{ config.ui.buffer_max_item }}
                    </el-button>
                </el-row>
                <el-drawer v-model="drawer" title="扩展命令" :with-header="false" direction="btt" :modal="false"
                    style="width:70%;margin:auto;">
                    <el-scrollbar>
                        <el-row>
                            <el-col :span="2">
                                HEX
                            </el-col>
                            <el-col :span="18">
                                字符串
                            </el-col>
                            <el-col :span="2">
                                \r\n
                            </el-col>
                            <el-col :span="2">
                                <el-button type="primary" plain circle size='small' @click="drawer = false">
                                    <el-icon-Close width="15px" />
                                </el-button>
                            </el-col>
                        </el-row>
                        <el-row v-for="(item, index) in cmds" :key="index" :gutter="0" class="cmd_item">
                            <el-col :span="2">
                                <el-button size='small' :type="item.hex ? 'success' : 'info'"
                                    @click="item.hex = !item.hex" />
                            </el-col>
                            <el-col :span="18">
                                <el-input v-model="item.value" size="small" />
                            </el-col>
                            <el-col :span="2">
                                <el-button size='small' :type="item.newline ? 'success' : 'info'"
                                    @click="item.newline = !item.newline" />
                            </el-col>
                            <el-col :span="2">
                                <el-button type="primary" plain @click="send_cmd(index)">
                                    发送
                                </el-button>
                            </el-col>
                        </el-row>
                        <el-row justify="center">
                            <el-button type="primary" circle @click="cmds.push({ value: '', hex: false, newline: false })">
                                <el-icon-Plus width="20px" />
                            </el-button>
                        </el-row>
                    </el-scrollbar>
                </el-drawer>
            </el-footer>
        </el-container>
        <el-aside width="250px">
            <TextRight :configs="config" />
        </el-aside>
    </el-container>
</template>

<script setup lang="ts">
import TextRight from './TextRight.vue';
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
const props = defineProps({
    configs: {
        type: Object,
        default: {
        }
    }
})
const config = toRef(props.configs)
const fps = ref(0)
const scrollbarRef = ref(null)
const scroller = ref(null)
const drawer = ref(false)
const times = ref({
    refresh: 0,
    stress_rx: '',
    stress_tx: 0,
})
const cmds = ref([
    { value: "AT", hex: false, newline: true },
    { value: "AT+RST", hex: false, newline: true },
    { value: "AT+UART?", hex: false, newline: true },
    { value: "AT+UART=115200,8,1,0", hex: false, newline: true },
    { value: "AT+UART=115200,8,1,1", hex: false, newline: true },
])

const buffer = ref([])
// const show_buffer = ref([])
const _show_buffer_ = []
const show_buffer = reactive(_show_buffer_)
let buffer_id = 0;
let show_buffer_id = 0;

const clear_buffer = () => {
    buffer.value.splice(0, buffer.value.length)
    show_buffer.splice(0, show_buffer.length)
    buffer_id = 0;
    show_buffer_id = 0;
}

const send_text = ref({
    textarea: "",
    hex: false,
    send: {
        sum: 0,
        success: 0,
    },
    recv: {
        sum: 0,
        success: 0,
    }
})
const loop_send = ref({
    enable: false,
    time_ms: 1000
});
const content_config = ref({
    autoScrollToEnd: {
        label: '自动滚动到底部',
        value: true
    },
    autoRefresh: {
        label: '自动刷新',
        value: true
    },
    showNewLine: {
        label: '启用换行',
        value: true
    },
    showTime: {
        label: '显示时间',
        value: true
    },
    showText: {
        label: '显示文本',
        value: true
    },
    showHex: {
        label: '显示HEX',
        value: false
    },
    showHexdump: {
        label: '显示HEXDUMP',
        value: false
    },
    shellMode: {
        label: '终端模式',
        value: false
    },
})

const item_height = computed(() => {
    let height = 25;
    if (content_config.value.showHex.value)
        height += 25;
    if (content_config.value.showHexdump.value)
        return null
    return height;
})

const show_items = computed(() => {
    if (!config.value.listen) {
        const max = config.value.ui.buffer_max_item
        if (buffer.value.length != max)
            return buffer.value;
        let arr = []
        arr.push(...buffer.value.slice(buffer_id % max, max))
        arr.push(...buffer.value.slice(0, buffer_id % max))
        return arr
    }
    return show_buffer;
})

function numberToStringHaveZero(num: number, len: number) {
    return String("0".repeat(len) + num).slice(-len);
}

function stringToHex(str) {
    let hex = '';
    for (let i = 0; i < str.length; i++) {
        let charCode = str.charCodeAt(i);
        let hexValue = charCode.toString(16).toUpperCase().padStart(2, '0');
        hex += ' ' + hexValue;
    }
    return hex;
}

function arrayToHexString(str, len) {
    let hex_str = '';
    for (let i = 0; i < str.length; i++) {
        let charCode = str.charCodeAt(i);
        if (31 < charCode && charCode < 127) {
            hex_str += String.fromCharCode(charCode);
        } else {
            hex_str += '.';
        }
    }
    for (let i = hex_str.length; i < len; i++) {
        hex_str += '.';
    }
    return hex_str;
}

function splitStringBySize(str, size) {
    let result = [];
    for (let i = 0; i < str.length; i += size) {
        result.push(str.substr(i, size));
    }
    return result;
}

const scrollerGoToBottom = () => {
    if (!content_config.value.autoScrollToEnd.value)
        return
    const content = scroller.value
    content?.scrollToBottom()
    scrollbarRef.value?.setScrollTop(content?.$el.clientHeight)
}

const send_cmd = (index: number) => {
    console.log('buffer len ', buffer.value.length)
}

let fps_count = 0;
const pushMsgToBuffer = (msg) => {
    if (msg.type.toUpperCase() == 'RX')
        send_text.value.recv.sum += msg.value.length
    else
        send_text.value.send.sum += msg.value.length
    msg.id = buffer_id++;
    buffer.value[msg.id % config.value.ui.buffer_max_item] = msg
    fps_count++;
}

pushMsgToBuffer({ time: new Date().getTime(), value: "Hello Wireless tools", type: "RX" })

let loop_send_handle = null
const create_loop_tx_task = (enable: boolean) => {
    if (loop_send_handle != null) {
        clearInterval(loop_send_handle)
        loop_send_handle = null
    }
    if (enable) {
        loop_send_handle = setInterval(() => {
            const tick = new Date().getTime()
            if (config.value.listen && send_text.value.textarea.length)
                pushMsgToBuffer({ time: new Date().getTime(), value: send_text.value.textarea, type: "TX" })
            times.value.stress_tx = new Date().getTime() - tick
        }, loop_send.value.time_ms)
    }
}

let stress_test_rx_handle = null;
const create_stress_test_rx_task = (start: boolean = true) => {
    if (stress_test_rx_handle != null) {
        clearInterval(stress_test_rx_handle)
        stress_test_rx_handle = null;
    }
    if (start)
        stress_test_rx_handle = setInterval(() => {
            const tick = new Date().getTime()
            if (config.value.listen)
                pushMsgToBuffer({ time: new Date().getTime(), value: "Hello Wireless tools", type: "RX" })
            times.value.stress_rx = new Date().getTime() - tick
        }, 10);
}
create_stress_test_rx_task();

console.log(show_buffer)

/* 60HZ 刷新率 */
let refresh_handle = null;
const create_refresh_task = (start: boolean = true) => {
    if (refresh_handle != null) {
        clearInterval(refresh_handle)
        refresh_handle = null;
    }
    if (start)
        refresh_handle = setInterval(() => {
            let cur_id = buffer_id
            if (content_config.value.autoRefresh.value && cur_id != show_buffer_id) {
                const tick = new Date().getTime()
                const show_item_max = config.value.ui.show_max_item
                const item_max = config.value.ui.buffer_max_item
                const show = show_buffer
                const cache = buffer.value
                let add_item = cur_id - show_buffer_id
                let remove_len = 0;
                if (add_item >= show_item_max) {
                    /* 长数据需要进一步优化, 目前1000个 */
                    show_buffer_id = cur_id - show_item_max
                    for (let i = 0; i < show_item_max; i++) {
                        show[i] = cache[(show_buffer_id + i) % item_max]
                    }
                    show_buffer_id = cur_id;
                }
                else {
                    let start_index = show.length
                    let need_push = add_item;
                    if (show.length + add_item > show_item_max) {
                        remove_len = (show.length + add_item) % show_item_max;
                        need_push = add_item - remove_len
                        if (need_push < 0)
                            need_push = 0;
                    }

                    if (need_push > 0) {
                        /* 长数据需要进一步优化, 目前1000个 */
                        for (let i = 0; i < need_push; i++)
                            show.push(cache[(show_buffer_id++ % item_max)])
                    }

                    if (remove_len > 0) {
                        remove_len -= need_push
                        if (remove_len > 0) {
                            let index = 0;
                            /* 修改原数组不会触发vue监控, 不会更新页面, 提升性能(比splice(0, remove_len) 快 ) */
                            for (let i = remove_len; i < show_item_max; i++) {
                                _show_buffer_[index++] = _show_buffer_[i];
                            }
                        }
                    }

                    add_item -= need_push
                    if (add_item > 0) {
                        start_index = show_item_max - add_item;
                        /* 此处使用 show[i] 更新 会触发 VUE 监控, 从而更新界面 */
                        for (let i = start_index; i < show_item_max; i++)
                            show[i] = cache[(show_buffer_id++) % item_max]
                    }
                }

                scrollerGoToBottom()
                times.value.refresh = new Date().getTime() - tick
            }
        }, config.value.ui.refresh)
}
create_refresh_task();

let fps_handle = null;
const create_fps_monitor = (start: boolean = true) => {
    if (fps_handle != null) {
        clearInterval(fps_handle)
        fps_handle = null;
    }
    if (start)
        fps_handle = setInterval(() => {
            fps.value = fps_count;
            fps_count = 0;
        }, 1000)
}
create_fps_monitor();
</script>

<style scoped>
.el-container {
    height: 100% !important;
    width: 99% !important;
    margin: 0 auto;
    background-color: white !important;
}

.el-header {
    height: 35px;
}

.text-content__title {
    display: flex;
    padding: 5px 0;
}

.el-checkbox {
    margin-right: 5px;
}

.el-main {
    width: 98%;
    padding: 5px;
    margin: 0 auto;
    box-shadow: #0000003d 0px 0px 10px;
    border-radius: 3px;
}

.scroller {
    height: 100%;
    text-align: start;
}

.hexdump {
    height: auto;
    font-size: 0.9em;
}

.hexdump__item {
    display: inline-block;
    font-family: 'Courier New', Courier, 'Lucida Console', Consolas, monospace;
}

.el-footer {
    height: auto;
    padding: 10px 5px;
    min-height: 80px;
}

.text-content-info {
    margin-top: 5px;
    margin-bottom: 0;
    padding: 0px 10px;
    justify-content: flex-end;
}

.text-content-info .el-button {
    height: 10px;
    font-size: 0.5em;
    margin-left: 5px;
    padding: 10px 5px;
}

:deep(.el-drawer__body) {
    padding: 10px
}

.cmd_item {
    margin-bottom: 0px;
}
</style>